# 仅为语音转文字的接口 asr接口里面写了如何传变量

import os
import uuid
import time
from flask import Flask, request, jsonify, Response
import dashscope

# ===================== 基础配置 =====================
app = Flask(__name__)
app.json.ensure_ascii = False  # 控制中文是否转成 \uXXXX

# 临时保存上传文件的目录（仅当 audio 为本地文件上传时使用）
app.config['UPLOAD_FOLDER'] = os.environ.get('UPLOAD_FOLDER', 'uploads')
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

# 从环境变量读取 DashScope 的 API Key（安全起见，不要写死在代码里）
# 使用前在系统里设置：export DASHSCOPE_API_KEY=你的密钥
dashscope.api_key = 'sk-b42f646a808549e099932167d32f2a9c'

# 默认 ASR 模型（可按需替换）
DEFAULT_MODEL = os.environ.get('ASR_MODEL', 'qwen-audio-asr')

# 允许的音频扩展名（主要用于文件上传场景做基本校验）
ALLOWED_EXTS = {'.wav', '.mp3', '.m4a', '.flac', '.aac', '.ogg', '.opus'}


# ===================== 工具函数 =====================
def allow_file(filename: str) -> bool:
    """判断文件扩展名是否在白名单内"""
    return os.path.splitext(filename)[1].lower() in ALLOWED_EXTS


def ok(data):
    """统一成功响应"""
    return jsonify({"code": 0, "message": "ok", "data": data})


def fail(msg, code=1, http_status=400):
    """统一失败响应"""
    return jsonify({"code": code, "message": msg, "data": None}), http_status


def get_audio_param():
    """
    统一读取“一个变量：audio”
    返回二元组：(audio_uri, local_path)
    - audio_uri: 传给 DashScope 的音频 URI（可能是 http(s) 或 file://）
    - local_path: 若为本地临时文件，返回其绝对路径；否则为 None
    说明：
      1) multipart/form-data:
           - 文件字段：request.files['audio'] 是 FileStorage -> 保存到本地，返回 file:// 路径
           - 也允许 form 文本字段 audio=URL（少见，用于方便统一）
      2) application/json:
           - JSON 结构 {"audio": "https://..."} -> 直接返回 URL
    """
    audio_uri = None
    local_path = None

    # 情况 A：multipart/form-data
    if request.content_type and "multipart/form-data" in request.content_type.lower():
        # A1: 文件字段
        if "audio" in request.files and request.files["audio"].filename:
            f = request.files["audio"]
            # 基本校验扩展名（防止误传非音频文件）
            if not allow_file(f.filename):
                return None, None, f"不支持的文件类型，仅支持：{', '.join(sorted(ALLOWED_EXTS))}"
            ext = os.path.splitext(f.filename)[1].lower()
            filename = f"{uuid.uuid4().hex}{ext}"
            local_path = os.path.abspath(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            f.save(local_path)
            audio_uri = f"file://{local_path}"
            return audio_uri, local_path, None

        # A2: 表单文本字段（将 audio 视为 URL 字符串）
        audio_text = request.form.get("audio")
        if audio_text:
            audio_text = audio_text.strip()
            if audio_text.startswith(("http://", "https://", "file://")):
                return audio_text, None, None
            return None, None, "audio 文本必须以 http(s):// 或 file:// 开头"

        # 未提供 audio
        return None, None, "请通过字段 'audio' 提供音频文件或 URL"

    # 情况 B：application/json
    if request.is_json:
        payload = request.get_json(silent=True) or {}
        audio_text = (payload.get("audio") or "").strip()
        if not audio_text:
            return None, None, "JSON 中缺少 'audio' 字段或值为空"
        if audio_text.startswith(("http://", "https://", "file://")):
            return audio_text, None, None
        return None, None, "audio 必须是以 http(s):// 或 file:// 开头的可访问 URL"

    # 其它 Content-Type（如 x-www-form-urlencoded 等）：
    # 也可以尝试从同名字段读取文本 URL，方便调用方统一
    audio_text = (request.values.get("audio") or "").strip()
    if audio_text:
        if audio_text.startswith(("http://", "https://", "file://")):
            return audio_text, None, None
        return None, None, "audio 必须是以 http(s):// 或 file:// 开头的可访问 URL"

    return None, None, "未检测到有效的 audio 参数（支持文件上传或 URL）"


# ===================== 路由 =====================
@app.route("/health", methods=["GET"])
def health():
    """健康检查"""
    return ok({"service": "asr", "status": "healthy"})

@app.route("/asr", methods=["POST"])
def asr():
    """
    语音识别接口（asr）
    调用方法（统一只用一个变量：audio）：
      1) 上传本地音频文件：
         windows下：
         输出全部：curl -X POST "https://u300015-9b1c-8481050b.bjc1.seetacloud.com:8443/asr" -H "Expect:" -F "audio=@E:\1.mp3"
         仅输出文本：curl -X POST "https://u300015-9b1c-8481050b.bjc1.seetacloud.com:8443/asr?simple=1" -H "Expect:" -F "audio=@E:\1.mp3"
      2) 传 OSS 公开 URL（或任意 http(s) URL）：
         2.1 JSON：
           curl -X POST http://localhost:6006/asr \
             -H "Content-Type: application/json" \
             -d '{"audio":"https://your-bucket.oss-cn-xxx.aliyuncs.com/test.m4a"}'
         2.2 multipart 表单文本：
           curl -X POST http://localhost:6006/asr \
             -F "audio=https://your-bucket.oss-cn-xxx.aliyuncs.com/test.m4a"

    返回：
      {
        "code": 0,
        "message": "ok",
        "data": {
          "text": "识别出的文本",
          "model": "qwen-audio-asr",
          "duration_ms": 842
        }
      }
    """
    # 基础校验：API Key
    if not dashscope.api_key:
        return fail("服务器未配置 DASHSCOPE_API_KEY 环境变量", http_status=500)

    # 统一读取 audio（可能是 file://，也可能是 http(s)://）
    audio_uri, local_path, err = get_audio_param()
    if err:
        return fail(err)

    # 调用 ASR 模型
    start = time.time()
    try:
        response = dashscope.MultiModalConversation.call(
            model=DEFAULT_MODEL,
            messages=[{
                "role": "user",
                "content": [{"audio": audio_uri}]
            }],
            result_format="message"
        )
    except Exception as e:
        # 若使用了本地临时文件，失败时也尝试清理
        if local_path and os.path.exists(local_path):
            try:
                os.remove(local_path)
            except Exception:
                pass
        return fail(f"DashScope 调用失败：{str(e)}", http_status=502)

    duration_ms = int((time.time() - start) * 1000)

    # 解析模型返回
    text = None
    parse_err = None
    try:
        text = response['output']['choices'][0]['message']['content'][0]['text']
    except Exception as e:
        parse_err = f"解析模型返回失败：{str(e)}"

    # 清理临时文件（若有）
    if local_path and os.path.exists(local_path):
        try:
            os.remove(local_path)
        except Exception:
            pass

    if text is None:
        return fail(parse_err or "ASR 识别失败，未返回文本", http_status=502)
    # === 新增：simple 模式 ===
    simple_mode = request.args.get("simple", "").lower() in ("1", "true", "yes")
    if simple_mode:
        return Response(text, mimetype="text/plain; charset=utf-8")
    return ok({
        "text": text,
        "model": DEFAULT_MODEL,
        "duration_ms": duration_ms
    })


# ===================== 启动（开发模式） =====================
if __name__ == "__main__":
    # 生产环境建议使用 gunicorn/uwsgi 部署，并在前面加 Nginx 做反向代理与限流
    host = os.environ.get("HOST", "0.0.0.0")
    port = int(os.environ.get("PORT", "6006"))
    debug = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
    app.run(host=host, port=port, debug=debug)
